package com.lsh.oms.core.records;

import com.google.common.collect.Lists;
import com.lsh.base.lang.Maps;
import com.lsh.base.lang.json.Jsons;
import com.lsh.oms.core.dao.other.ReturnMapper;
import com.lsh.oms.core.dao.other.ReturnRequestMapper;
import com.lsh.oms.core.exception.BusinessException;
import com.lsh.oms.core.records.core.ReturnRepository;
import com.lsh.oms.core.records.core.*;
import com.lsh.oms.core.service.order.OrderQueryService;
import com.lsh.oms.core.service.tool.Criteria;
import com.lsh.oms.core.service.tool.Pageable;
import com.lsh.oms.core.utils.BatchNumberGenerator;
import com.lsh.oms.core.utils.IdGenerator;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.lsh.base.lang.BigDecimals.*;
import static com.lsh.base.lang.bean.Beans.*;
import static java.math.BigDecimal.ZERO;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

public class ReturnRequestRecord implements ReturnRequest, JsonType {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReturnRequestRecord.class);

    @Resource
    private ReturnRequestMapper mapper;

    @Resource
    private ReturnMapper returnMapper;

    @Resource
    private ReturnRepository returns;

    @Resource
    private ReturnResultRepository returnResults;

    @Resource
    private BatchNumberGenerator numberGenerator;

    @Resource
    private OrderQueryService orders;

    protected Long id;

    protected String number;

    protected Long orderId;

    protected Long shippingId;

    protected Long receiptId;

    protected Long returnId;

    protected Long zoneId;

    protected Long addressId;

    protected String warehouseId;

    protected String warehouseName;

    protected Occasion occasion;

    protected Long userId;

    protected String username;

    protected String shippingDeliveryWay;

    protected Map<String, Object> ext;

    protected Boolean checkImmediately;

    protected Status status;

    protected Map<String, Object> refundNotifyRequest;

    protected DateTime createdAt;

    protected DateTime updatedAt;

    protected Long venderId;

    protected Long partyId;

    protected String partyUsername;

    protected String partyType;

    protected Long checkPartyId;

    protected String checkPartyUsername;

    protected String checkPartyType;

    protected Boolean approved;

    protected String returnState;

    protected List<ReturnRequestRecord.ReturnRequestDetailRecord> details;

    @Override
    public Map<String, Object> toJson() {
        Map<String, Object> json = new HashMap<>();
        json.put("id", id);
        Optional.ofNullable(number).ifPresent($ -> json.put("number", $));

        json.put("receipt", Maps.newHashMap("id", receiptId));

        Map<String, Object> order = Maps.newHashMap("id", orderId);
        Optional.ofNullable(getMap(ext, "order")).ifPresent(order::putAll);
        json.put("order", order);

        Map<String, Object> shipping = Maps.newHashMap("id", shippingId, "delivery_way", shippingDeliveryWay);
        Optional.ofNullable(getMap(ext, "shipping")).ifPresent(shipping::putAll);
        json.put("shipping", shipping);


        Map<String, Object> user = Maps.newHashMap("id", userId);
        Optional.ofNullable(getMap(ext, "user")).ifPresent(user::putAll);
        json.put("user", user);

        json.put("occasion", occasion);
        json.put("status", status);
        json.put("created_at", createdAt);
        json.put("updated_at", updatedAt);
        json.put("vender_id", venderId);

        Optional.ofNullable(returnId).ifPresent($ -> json.put("return", Maps.newHashMap("id", $)));

        if (partyUsername != null) {
            json.put("party", Maps.newHashMap("id", partyId, "username", partyUsername, "type", partyType));
        } else {
            json.put("party", Maps.newHashMap("id", 0L, "username", "送货司机", "type", "DELIVERYMAN"));
        }

        Optional.ofNullable(checkPartyUsername).ifPresent($ -> {
            json.put("check_party", Maps.newHashMap("id", checkPartyId, "username", checkPartyUsername, "type", checkPartyType));
        });

        json.put("warehouse", Maps.newHashMap("id", warehouseId, "name", warehouseName));

        Optional.ofNullable(approved).ifPresent($ -> json.put("approved", approved));

        Optional.ofNullable(returnState).ifPresent($ -> json.put("return_state", returnState));

        ofNullable(details).ifPresent(details -> json.put("details", details.stream().map(item -> ((JsonType) item).toJson()).collect(toList())));
        return json;
    }

    @Override
    public Long id() {
        return id;
    }

    @Override
    public String number() {
        return number;
    }

    @Override
    public Status status() {
        return status;
    }

    @Override
    public void start(Map<String, Object> request) {
        if (checkImmediately()) {
            check(request);
        }
    }

    private boolean checkImmediately() {
        return checkImmediately || "DIRECT".equals(shippingDeliveryWay);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void check(Map<String, Object> request) {
        LOGGER.info("return request: {} check with request: {}", id, Jsons.toJsonString(request));
        ensureStatusIsApplicable();
        ensureActualQuantityNotOver(request);
        opinionOnDetails(request);
        createReturns();
        changeStatus(request);
    }

    private void changeStatus(Map<String, Object> request) {
        this.status = Status.CHECKED;
        this.approved = getList(request, "details").stream().anyMatch($ -> getBoolean($, "opinion.approved", true));
        this.returnState = this.approved ? returnState() : null;
        int count = mapper.update(this, request, Status.PENDING);
        if (count != 1) {
            throw BusinessException.invalidStatus();
        }
    }

    private String returnState() {
        BigDecimal actualQuantity = details.stream().filter($ -> $.opinionApproved && !$.opinionDiscarded).map($ -> $.actualQuantity).reduce(ZERO, BigDecimal::add);
        if (lessThanOrEqualZero(actualQuantity)) {
            return "NONE";
        }
        BigDecimal quantity = details.stream().map($ -> $.quantity).reduce(ZERO, BigDecimal::add);
        return greaterThan(quantity, actualQuantity) ? "PART_OF" : "WHOLE";
    }

    private void createReturns() {
        this.details = findDetails(Lists.newArrayList(id));

        Map<Boolean, List<ReturnRequestDetailRecord>> group = details.stream()
                .filter($ -> $.opinionApproved && greaterThanZero($.actualQuantity))
                .collect(Collectors.groupingBy($ -> $.opinionDiscarded));

        group.forEach((k, v) -> {
            Long returnId = IdGenerator.genId();

            Map<String, Object> ext = new HashMap<>(this.ext);
            ext.put("return_request", Maps.newHashMap("id", this.id, "created_at", this.createdAt, "check_immediately", this.checkImmediately));

            Boolean returnRequired = !k;
            if (returnRequired) {
                this.returnId = returnId;
            }

            returnMapper.insert(this, Maps.newHashMap("number", (returnRequired ? numberGenerator.generateReturnNumber(orderId) : null), "return_required", returnRequired, "id", returnId, "ext", ext, "shipping_delivery_way", shippingDeliveryWay));
            returnMapper.insertDetails(this, v.stream()
                    .map($ -> {
                        Map<String, Object> detailExt = new HashMap<>($.ext);
                        detailExt.put("return_request_detail", Maps.newHashMap("quantity", $.quantity, "actual_quantity", $.actualQuantity));
                        detailExt.put("reason", Maps.newHashMap("code", $.reasonCode, "images", $.reasonImages, "notes", $.reasonNotes));
                        detailExt.put("opinion", Maps.newHashMap(
                                "approved", $.opinionApproved,
                                "discarded", $.opinionDiscarded,
                                "discarded_reason", $.opinionDiscardedReason,
                                "take_responsibility", $.opinionTakeResponsibility,
                                "notes", $.opinionNotes)
                        );
                        return Maps.newHashMap("id", IdGenerator.genId(), "return_id", returnId, "return_request_detail", $, "ext", detailExt);
                    })
                    .collect(toList()));
            Return created = returns.find(returnId).orElseThrow(RuntimeException::new);
            Optional.ofNullable(refundNotifyRequest).filter($ -> !$.isEmpty()).ifPresent($ -> created.refundNotify(refundNotifyRequest));
            created.start();
        });
    }

    private void opinionOnDetails(Map<String, Object> request) {
        getList(request, "details").forEach($ -> mapper.updateDetail($));
    }

    private void ensureActualQuantityNotOver(Map<String, Object> request) {
        Map<Long, ReturnRequestDetailRecord> groupById = details.stream().collect(toMap($ -> $.id(), $ -> $));
        boolean overQuantity = getList(request, "details").stream().anyMatch($ -> greaterThan(getBigDecimal($, "quantity"), Optional.ofNullable(groupById.get(getLong($, "id"))).orElseThrow(BusinessException::badRequest).quantity));
        if (overQuantity) {
            throw BusinessException.badRequest();
        }
    }

    private List<ReturnRequestRecord.ReturnRequestDetailRecord> findDetails(Collection<Long> returnIds) {
        List<ReturnRequestDetailRecord> details = mapper.findDetails(returnIds);
        details.forEach($ -> $.returnRequest = this);
        return details;
    }

    @Override
    public Optional<ReturnRequestDetail> detail(Long id) {
        ReturnRequestDetail detail = mapper.findDetailById(id);
        if (detail == null) {
            return Optional.empty();
        }
        ((ReturnRequestDetailRecord) detail).returnRequest = this;
        return Optional.of(detail);
    }

    @Override
    public Map<String, List<Map<String, Object>>> relations() {
        Map<String, List<Map<String, Object>>> result = new HashMap<>();
        result.put("orders", orders.findOrders(orderId));
        result.put("shippings", orders.findShippings(shippingId));
        result.put("returns", findReturns());
        result.put("return_results", findReturnResults());
        return result;
    }

    @Transactional
    @Override
    public void refundNotify(Map<String, Object> request) {
        LOGGER.info("return request: {} refund notify request: {}", id, Jsons.toJsonString(request));
        if (Maps.isEmpty(this.refundNotifyRequest)) {
            this.refundNotifyRequest = request;
            mapper.updateRefundNotifyRequest(request, id);
        }
    }

    private List<Map<String, Object>> findReturns() {
        if (status == Status.CHECKED) {
            Criteria criteria = new Criteria();
            criteria.add("return_request_id", id.toString());
            Pageable<Return> result = returns.query(criteria, 1L, 10L);
            return result.getEntries().stream().map($ -> Maps.of("id", $.id(), "number", $.number(), "status", $.status())).collect(toList());
        } else {
            return Collections.emptyList();
        }
    }

    private List<Map<String, Object>> findReturnResults() {
        if (status == Status.CHECKED) {
            Criteria criteria = new Criteria();
            criteria.add("return_request_id", id.toString());
            Pageable<ReturnResult> result = returnResults.query(criteria, 1L, 10L);
            return result.getEntries().stream().map($ -> Maps.of("id", $.id(), "number", $.number(), "status", $.status())).collect(toList());
        } else {
            return Collections.emptyList();
        }
    }

    private void ensureStatusIsApplicable() {
        if (!status.equals(Status.PENDING)) {
            throw BusinessException.badRequest("当前状态不可进行审批");
        }
    }

    public static class ReturnRequestDetailRecord implements ReturnRequestDetail, JsonType {

        @Resource
        private ReturnRequestMapper mapper;

        protected Long id;

        protected ReturnRequestRecord returnRequest;

        protected Long returnRequestId;

        protected Integer itemCode;

        protected Long skuId;

        protected String skuName;

        protected Boolean isWeighingGoods;

        protected BigDecimal quantity;

        protected BigDecimal actualQuantity;

        protected BigDecimal deliveredQuantity;

        protected BigDecimal saleUnit;

        protected BigDecimal price;

        protected String reasonCode;

        protected List<String> reasonImages;

        protected String reasonNotes;

        protected Boolean opinionApproved;

        protected Boolean opinionDiscarded;

        protected String opinionDiscardedReason;

        protected String opinionTakeResponsibility;

        protected String opinionNotes;

        protected Map<String, Object> ext;

        protected DateTime createdAt;

        protected DateTime updatedAt;

        public Integer getItemCode() {
            return itemCode;
        }

        public BigDecimal deliveredQuantity() {
            return deliveredQuantity;
        }

        @Override
        public Map<String, Object> toJson() {
            Map<String, Object> json = new HashMap<>();
            json.put("id", id);
            json.put("item_code", itemCode);
            json.put("sku", Maps.newHashMap("id", skuId, "name", skuName));
            json.put("is_weighing_goods", isWeighingGoods);
            json.put("quantity", quantity);
            json.put("actual_quantity", actualQuantity);
            json.put("sale_unit", saleUnit);
            json.put("reason", Maps.newHashMap("code", reasonCode, "images", reasonImages, "notes", reasonNotes));

            Map<String, Object> opinion = new HashMap<>();
            Optional.ofNullable(opinionApproved).ifPresent($ -> opinion.put("approved", opinionApproved));
            Optional.ofNullable(opinionDiscarded).ifPresent($ -> opinion.put("discarded", opinionDiscarded));
            Optional.ofNullable(opinionDiscardedReason).ifPresent($ -> opinion.put("discarded_reason", opinionDiscardedReason));
            Optional.ofNullable(opinionTakeResponsibility).ifPresent($ -> opinion.put("take_responsibility", opinionTakeResponsibility));
            Optional.ofNullable(opinionNotes).ifPresent($ -> opinion.put("notes", opinionNotes));

            if (!opinion.isEmpty()) {
                json.put("opinion", opinion);
            }
            json.put("created_at", createdAt);
            json.put("updated_at", updatedAt);
            return json;
        }

        @Override
        public Long id() {
            return id;
        }

        @Override
        public void update(Map<String, Object> request) {
            if (returnRequest.status != Status.CHECKED) {
                throw BusinessException.badRequest("仅能对已审核条目进行该操作");
            }
            request.put("id", id);
            mapper.updateDetail(request);
        }
    }
}
